/*
 * Copyright 2009-2010 Nanjing RedOrange ltd (http://www.red-orange.cn)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package redora.generator;

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.core.ParseException;
import freemarker.ext.dom.NodeModel;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import redora.generator.Template.Destination;

import javax.xml.transform.TransformerException;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

import static java.io.File.separator;
import static java.io.File.separatorChar;
import static redora.generator.GenerateMojo.GENERATION_TARGET;
import static redora.generator.Template.Destination.redora;
import static redora.generator.XMLUtil.xsltTransform;

/**
 * This is a wrapper for the different template engines. Now we can use Freemarker,
 * XSLT and file copy.
 *
 * @author Nanjing RedOrange (http://www.red-orange.cn)
 */
class TemplateProcessor {

    final Configuration freemarkerConf = new Configuration();
    final GeneratorTemplate tHandler;
    final FileLocations where;

    /**
     * @param where (Mandatory) A set of directories: for example the model location
     * @throws ModelGenerationException When there are no models
     */
    public TemplateProcessor(@NotNull FileLocations where) throws ModelGenerationException {
        this.where = where;
        this.tHandler = new GeneratorTemplate(where.resourceDir);

        prepareFreemarker();
    }

    void prepareFreemarker() throws ModelGenerationException {
//        try {
//            NodeModel.useJaxenXPathSupport();
//        } catch (Exception e) {
//            throw new ModelGenerationException("Can't initialize xpath support", e);
//        }
        ClassTemplateLoader custom = new ClassTemplateLoader(getClass(), "/templates");
        ClassTemplateLoader standard = new ClassTemplateLoader(getClass(), "/");
        File tplDir = new File(".." + separatorChar + "p-templates" + separatorChar
                + where.resourceDir);
        if (!tplDir.exists()) {
            tplDir = new File(where.localTemplatesDir);
        }
        try {
            FileTemplateLoader local = new FileTemplateLoader(tplDir);
            freemarkerConf.setTemplateLoader(new MultiTemplateLoader(new TemplateLoader[]{local,
                    custom, standard}));
        } catch (IOException e) {
            freemarkerConf.setTemplateLoader(new MultiTemplateLoader(new TemplateLoader[]{custom,
                    standard}));
        }
        freemarkerConf.setTemplateUpdateDelay(9000);
        //freemarkerConf.addAutoInclude("includeMacro.ftl");
    }

    /**
     * Creates the generated source file based on given template and model
     * document.
     */
    public void process(@NotNull Template tpl, @NotNull Document model, @NotNull String packageName
                        , @NotNull String outFileName, Map<String, String> xsltParam, String artifact)
            throws ModelGenerationException {
        System.out.print("Processing with " + tpl.getTemplateFileName());

        String destinationPath;

        switch (tpl.destination) {
            case target:
                if (tpl.path == null) {
                    destinationPath = where.buildDir + separatorChar + "generated-sources" + separatorChar
                            + GENERATION_TARGET;
                } else {
                    destinationPath = where.buildDir;
                }
                break;
            case source:
                if (tpl.path == null) {
                    destinationPath = where.sourceDir;
                } else {
                    destinationPath = "src";
                }
                break;
            case redora:
                destinationPath = where.redoraDir;
                break;
            default:
                throw new IllegalArgumentException("Unused destination " + tpl.destination);
        }

        if (tpl.path == null) {
            if (tpl.destination == redora)
                destinationPath  += separator + artifact;
            else
                destinationPath += separator + packageName.replace('.', separatorChar);
        } else {
            destinationPath += separator + tpl.path.replace('/', separatorChar).replace('\\', separatorChar);
        }
        System.out.println(" to " + destinationPath + "..." + outFileName);

        if (tpl.destination == Destination.source) {
            if (new File(destinationPath, outFileName).exists()) {
                System.out.println("Stub " + outFileName + " already exists.");
                return;
            }
        }

        // -------> Prepare Output
        //noinspection ResultOfMethodCallIgnored
        new File(destinationPath).mkdirs();
        Writer out;
        try {
            out = new FileWriter(new File(destinationPath, outFileName));
        } catch (IOException e) {
            throw new ModelGenerationException("Can't find: " + destinationPath + separatorChar + outFileName, e);
        }

        switch (tpl.type) {
            case freemarker:
                // Input ----->
                Map<String, NodeModel> root = new HashMap<String, NodeModel>();
                root.put("doc", NodeModel.wrap(model));

                try {
                    freemarker.template.Template template = freemarkerConf.getTemplate(tpl.getTemplateFileName());
                    template.process(root, out);
                } catch (ParseException e) {
                    throw new ModelGenerationException(
                            "There is an error in template: " + tpl.getTemplateFileName()
                                    + ". I found it when generating " + outFileName, e);
                } catch (IOException e) {
                    throw new ModelGenerationException("Can't find '" + tpl.getTemplateFileName()
                            + "' when generating " + outFileName, e);
                } catch (TemplateException e) {
                    throw new ModelGenerationException(
                            "There is an error in template: " + tpl.getTemplateFileName()
                                    + ". I found it when generating " + outFileName, e);
                } catch (RuntimeException e) {
                    throw new ModelGenerationException(
                            "There is another error while trying this template: "
                                    + tpl.getTemplateFileName() + ". I found it when generating "
                                    + outFileName, e);
                }
                break;
            case xslt:
                try {
                    xsltTransform(model.getFirstChild(), tHandler.findFile(tpl.getTemplateFileName()), out, xsltParam);
                } catch (FileNotFoundException e) {
                    throw new ModelGenerationException("Can't find " + tpl.getTemplateFileName(), e);
                } catch (TransformerException e) {
                    throw new ModelGenerationException("Sorry, i failed to use this template: "
                            + tpl.getTemplateFileName() + ". It broke when generating " + outFileName,
                            e);
                }
                break;
            case copy:
                try {
                    IOUtils.copy(tHandler.findFile(tpl.getTemplateFileName()), out);
                } catch (IOException e) {
                    throw new ModelGenerationException("File copy failed " + tpl.getTemplateFileName(), e);
                }
        }
        IOUtils.closeQuietly(out);
    }
}
